#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>
#include <errno.h>

static const char *TAG = "serial_port";

#ifdef __cplusplus
extern "C" {
#endif

static speed_t get_baudrate(jint baud_rate) {
    switch (baud_rate) {
        case 0:
            return B0;
        case 50:
            return B50;
        case 75:
            return B75;
        case 110:
            return B110;
        case 134:
            return B134;
        case 150:
            return B150;
        case 200:
            return B200;
        case 300:
            return B300;
        case 600:
            return B600;
        case 1200:
            return B1200;
        case 1800:
            return B1800;
        case 2400:
            return B2400;
        case 4800:
            return B4800;
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return B38400;
        case 57600:
            return B57600;
        case 115200:
            return B115200;
        case 230400:
            return B230400;
        case 460800:
            return B460800;
        case 500000:
            return B500000;
        case 576000:
            return B576000;
        case 921600:
            return B921600;
        case 1000000:
            return B1000000;
        case 1152000:
            return B1152000;
        case 1500000:
            return B1500000;
        case 2000000:
            return B2000000;
        case 2500000:
            return B2500000;
        case 3000000:
            return B3000000;
        case 3500000:
            return B3500000;
        case 4000000:
            return B4000000;
        default:
            return 0;
    }
}

JNIEXPORT jobject JNICALL
Java_com_embedsky_serialport_SerialPort_open(
        JNIEnv *env,
        jclass clazz,
        jstring path,
        jint baudrate
) {
    speed_t baud_speed = get_baudrate(baudrate);
    if (baud_speed == 0) {
        return NULL;
    }

    const char *device_path = env->GetStringUTFChars(path, NULL);
    if (device_path == NULL) {
        return NULL;
    }

    int file_descriptor = open(device_path, O_RDWR | O_NOCTTY);
    env->ReleaseStringUTFChars(path, device_path);

    if (file_descriptor < 0) {
        return NULL;
    }

    struct termios serial_config;
    if (tcgetattr(file_descriptor, &serial_config) != 0) {
        close(file_descriptor);
        return NULL;
    }

    cfmakeraw(&serial_config);
    cfsetispeed(&serial_config, baud_speed);
    cfsetospeed(&serial_config, baud_speed);

    if (tcsetattr(file_descriptor, TCSANOW, &serial_config) != 0) {
        close(file_descriptor);
        return NULL;
    }

    jclass file_desc_class = env->FindClass("java/io/FileDescriptor");
    jmethodID constructor = env->GetMethodID(file_desc_class, "<init>", "()V");
    jfieldID descriptor_field = env->GetFieldID(file_desc_class, "descriptor", "I");

    jobject file_desc_obj = env->NewObject(file_desc_class, constructor);
    env->SetIntField(file_desc_obj, descriptor_field, file_descriptor);

    return file_desc_obj;
}

JNIEXPORT void JNICALL
Java_com_embedsky_serialport_SerialPort_close(
        JNIEnv *env,
        jclass clazz
) {
    jclass serial_port_class = env->GetObjectClass(clazz);
    jfieldID fd_field = env->GetFieldID(serial_port_class, "mFd", "Ljava/io/FileDescriptor;");
    jobject file_desc_obj = env->GetObjectField(clazz, fd_field);

    jclass file_desc_class = env->FindClass("java/io/FileDescriptor");
    jfieldID desc_field = env->GetFieldID(file_desc_class, "descriptor", "I");
    jint fd = env->GetIntField(file_desc_obj, desc_field);

    close(fd);
}

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    return JNI_VERSION_1_4;
}

#ifdef __cplusplus
}
#endif